package eval

import (
	"showen7/inter"
	"showen7/num"
)

type Error struct {
	Message string
}

// Type returns the object type as a string
func (e *Error) Type() inter.ObjectType {
	return inter.ERROR_OBJ
}

// String returns the error message
func (e *Error) Inspect() string {
	return e.Message
}

func IsPairProc(args []inter.Object, env *inter.Environment) inter.Object {
	// Implementing the pair? predicate according to R4RS
	// pair? returns #t if obj is a pair, and otherwise returns #f
	if len(args) != 1 {
		return &Error{Message: "pair? requires exactly 1 argument"}
	}

	return &inter.BooleanObject{Value: args[0].Type() == inter.PAIR_OBJ}
}

// Type predicate functions according to R4RS
func IsBooleanProc(args []inter.Object, env *inter.Environment) inter.Object {
	if len(args) != 1 {
		return &Error{Message: "boolean? requires exactly 1 argument"}
	}

	return &inter.BooleanObject{Value: args[0].Type() == inter.BOOLEAN_OBJ}
}

func IsSymbolProc(args []inter.Object, env *inter.Environment) inter.Object {
	if len(args) != 1 {
		return &Error{Message: "symbol? requires exactly 1 argument"}
	}

	return &inter.BooleanObject{Value: args[0].Type() == inter.SYMBOL_OBJ}
}

func IsNumberProc(args []inter.Object, env *inter.Environment) inter.Object {
	if len(args) != 1 {
		return &Error{Message: "number? requires exactly 1 argument"}
	}

	return &inter.BooleanObject{Value: args[0].Type() == inter.NUMBER_OBJ}
}

func IsIntegerProc(args []inter.Object, env *inter.Environment) inter.Object {
	if len(args) != 1 {
		return &Error{Message: "integer? requires exactly 1 argument"}
	}

	if args[0].Type() != inter.NUMBER_OBJ {
		return &inter.BooleanObject{Value: false}
	}

	t := args[0].(*inter.NumberObject).Value.Type()
	return &inter.BooleanObject{Value: t == num.INTEGER_TYPE || t == num.BIGINTEGER_TYPE}
}

func IsRationalProc(args []inter.Object, env *inter.Environment) inter.Object {
	if len(args) != 1 {
		return &Error{Message: "rational? requires exactly 1 argument"}
	}

	if args[0].Type() != inter.NUMBER_OBJ {
		return &inter.BooleanObject{Value: false}
	}

	t := args[0].(*inter.NumberObject).Value.Type()
	return &inter.BooleanObject{Value: t == num.INTEGER_TYPE || t == num.BIGINTEGER_TYPE || t == num.RATIONAL_TYPE}
}

func IsRealProc(args []inter.Object, env *inter.Environment) inter.Object {
	if len(args) != 1 {
		return &Error{Message: "real? requires exactly 1 argument"}
	}

	if args[0].Type() != inter.NUMBER_OBJ {
		return &inter.BooleanObject{Value: false}
	}
	v := args[0].(*inter.NumberObject).Value
	t := v.Type()

	// Handle complex numbers with zero imaginary part
	if t == num.COMPLEX_TYPE {
		if imag(v.(num.Complex)) == 0 {
			return &inter.BooleanObject{Value: true}
		}
		return &inter.BooleanObject{Value: false}
	}

	return &inter.BooleanObject{Value: t == num.INTEGER_TYPE ||
		t == num.BIGINTEGER_TYPE ||
		t == num.RATIONAL_TYPE ||
		t == num.REAL_TYPE}
}

func IsComplexProc(args []inter.Object, env *inter.Environment) inter.Object {
	if len(args) != 1 {
		return &Error{Message: "complex? requires exactly 1 argument"}
	}

	if args[0].Type() != inter.NUMBER_OBJ {
		return &inter.BooleanObject{Value: false}
	}

	t := args[0].(*inter.NumberObject).Value.Type()
	return &inter.BooleanObject{Value: t == num.INTEGER_TYPE ||
		t == num.BIGINTEGER_TYPE ||
		t == num.RATIONAL_TYPE ||
		t == num.REAL_TYPE ||
		t == num.COMPLEX_TYPE}
}

func IsCharProc(args []inter.Object, env *inter.Environment) inter.Object {
	if len(args) != 1 {
		return &Error{Message: "char? requires exactly 1 argument"}
	}

	return &inter.BooleanObject{Value: args[0].Type() == inter.CHARACTER_OBJ}
}

func IsStringProc(args []inter.Object, env *inter.Environment) inter.Object {
	if len(args) != 1 {
		return &Error{Message: "string? requires exactly 1 argument"}
	}

	return &inter.BooleanObject{Value: args[0].Type() == inter.STRING_OBJ}
}

func IsProcedureProc(args []inter.Object, env *inter.Environment) inter.Object {
	if len(args) != 1 {
		return &Error{Message: "procedure? requires exactly 1 argument"}
	}

	objType := args[0].Type()
	return &inter.BooleanObject{Value: objType == inter.PROCEDURE_OBJ || objType == inter.BUILTIN_OBJ || objType == inter.LAMBDA_OBJ}
}

func Eq(args []inter.Object, env *inter.Environment) inter.Object {
	if len(args) != 2 {
		return &Error{Message: "eq? requires exactly 2 arguments"}
	}

	// In our implementation, we'll use pointer equality for eq?
	// This is a simplified version - a full implementation would need to check
	// if the objects are the same in memory
	a, b := args[0], args[1]

	// Same object reference
	if a == b {
		return &inter.BooleanObject{Value: true}
	}

	// Different types are never eq?
	if a.Type() != b.Type() {
		return &inter.BooleanObject{Value: false}
	}

	// For certain types, we need to check if they represent the same value
	// This is a simplified implementation - in a full R4RS implementation,
	// eq? would be more restrictive than eqv?
	switch a.Type() {
	case inter.BOOLEAN_OBJ:
		boolA := a.(*inter.BooleanObject)
		boolB := b.(*inter.BooleanObject)
		return &inter.BooleanObject{Value: boolA.Value == boolB.Value}

	case inter.NULL_OBJ:
		// All null objects are eq
		return &inter.BooleanObject{Value: true}

	case inter.SYMBOL_OBJ:
		// Symbols with the same name are eq
		symA := a.(*inter.SymbolObject)
		symB := b.(*inter.SymbolObject)
		return &inter.BooleanObject{Value: symA.Value == symB.Value}

	default:
		// For other types, only the same object (pointer equality) counts as eq
		return &inter.BooleanObject{Value: false}
	}
}

func Eqv(args []inter.Object, env *inter.Environment) inter.Object {
	if len(args) != 2 {
		return &Error{Message: "eqv? requires exactly 2 arguments"}
	}

	a, b := args[0], args[1]

	// Same object reference
	if a == b {
		return &inter.BooleanObject{Value: true}
	}

	// Different types are never eqv?
	if a.Type() != b.Type() {
		return &inter.BooleanObject{Value: false}
	}

	// For certain types, we need to check if they represent the same value
	switch a.Type() {
	case inter.BOOLEAN_OBJ:
		boolA := a.(*inter.BooleanObject)
		boolB := b.(*inter.BooleanObject)
		return &inter.BooleanObject{Value: boolA.Value == boolB.Value}

	case inter.NUMBER_OBJ:
		// For numbers, check if they have the same value
		return equalNumber(a, b)

	case inter.CHARACTER_OBJ:
		charA := a.(*inter.CharacterObject)
		charB := b.(*inter.CharacterObject)
		return &inter.BooleanObject{Value: charA.Value == charB.Value}

	case inter.STRING_OBJ:
		// For strings, check if they have the same value
		strA := a.(*inter.StringObject)
		strB := b.(*inter.StringObject)
		return &inter.BooleanObject{Value: strA.Value == strB.Value}

	case inter.SYMBOL_OBJ:
		// For symbols, check if they have the same name
		symA := a.(*inter.SymbolObject)
		symB := b.(*inter.SymbolObject)
		return &inter.BooleanObject{Value: symA.Value == symB.Value}

	case inter.NULL_OBJ:
		// All null objects are eqv
		return &inter.BooleanObject{Value: true}

	case inter.EMPTYLIST_OBJ:
		// All null objects are eqv
		return &inter.BooleanObject{Value: true}

	case inter.IDENTIFIER_OBJ:
		// Symbols with the same name are eqv
		idA := a.(*inter.IdentifierObject)
		idB := b.(*inter.IdentifierObject)
		return &inter.BooleanObject{Value: idA.Value == idB.Value}

	default:
		// For other types, only the same object (pointer equality) counts as eqv
		return &inter.BooleanObject{Value: false}
	}
}

func Equal(args []inter.Object, env *inter.Environment) inter.Object {
	if len(args) != 2 {
		return &Error{Message: "equal? requires exactly 2 arguments"}
	}

	return EqualRecursive(args[0], args[1])
}

func EqualRecursive(a, b inter.Object) inter.Object {
	// Same object reference
	if a == b {
		return &inter.BooleanObject{Value: true}
	}

	// Different types
	if a.Type() != b.Type() {
		return &inter.BooleanObject{Value: false}
	}

	// Handle different object types recursively
	switch a.Type() {
	case inter.BOOLEAN_OBJ:
		boolA := a.(*inter.BooleanObject)
		boolB := b.(*inter.BooleanObject)
		return &inter.BooleanObject{Value: boolA.Value == boolB.Value}

	case inter.NUMBER_OBJ:
		// Reuse the existing equal logic for numbers
		return equalNumber(a, b)

	case inter.CHARACTER_OBJ:
		charA := a.(*inter.CharacterObject)
		charB := b.(*inter.CharacterObject)
		return &inter.BooleanObject{Value: charA.Value == charB.Value}

	case inter.STRING_OBJ:
		strA := a.(*inter.StringObject)
		strB := b.(*inter.StringObject)
		return &inter.BooleanObject{Value: strA.Value == strB.Value}

	case inter.SYMBOL_OBJ:
		// For symbols, check if they have the same name
		symA := a.(*inter.SymbolObject)
		symB := b.(*inter.SymbolObject)
		return &inter.BooleanObject{Value: symA.Value == symB.Value}

	case inter.NULL_OBJ:
		// All null objects are equal
		return &inter.BooleanObject{Value: true}

	case inter.PAIR_OBJ:
		pairA := a.(*inter.PairObject)
		pairB := b.(*inter.PairObject)

		// Check for empty lists
		aIsNull := (pairA.Car == inter.EMPTY_LIST && pairA.Cdr == inter.EMPTY_LIST) ||
			(pairA.Car == nil && pairA.Cdr == nil)
		bIsNull := (pairB.Car == inter.EMPTY_LIST && pairB.Cdr == inter.EMPTY_LIST) ||
			(pairB.Car == nil && pairB.Cdr == nil)

		if aIsNull && bIsNull {
			return &inter.BooleanObject{Value: true}
		}

		if aIsNull || bIsNull {
			return &inter.BooleanObject{Value: false}
		}

		// Recursively compare car and cdr
		carEqual := EqualRecursive(
			convertToObjObject(pairA.Car),
			convertToObjObject(pairB.Car),
		)

		if !carEqual.(*inter.BooleanObject).Value {
			return carEqual
		}

		cdrEqual := EqualRecursive(
			convertToObjObject(pairA.Cdr),
			convertToObjObject(pairB.Cdr),
		)

		return cdrEqual

	default:
		// For other types, only the same object (pointer equality) counts as equal
		return &inter.BooleanObject{Value: false}
	}
}

func equalNumber(a, b inter.Object) inter.Object {

	numA := a.(*inter.NumberObject)
	numB := b.(*inter.NumberObject)

	return &inter.BooleanObject{Value: num.Equal(numA.Value, numB.Value)}
}

func convertToObjObject(value any) inter.Object {
	if value == nil {
		return inter.NULL_OBJECT
	}

	switch v := value.(type) {
	case *inter.PairObject:
		return v
	case *inter.EmptyList:
		return v
	case num.Number:
		return &inter.NumberObject{Value: v}
	case bool:
		return &inter.BooleanObject{Value: v}
	case rune:
		return &inter.CharacterObject{Value: v}
	case string:
		return &inter.StringObject{Value: v}
	case *inter.SymbolObject:
		return v
	case *inter.IdentifierObject:
		return v
	case *inter.NumberObject:
		return v
	case *inter.BooleanObject:
		return v
	case *inter.CharacterObject:
		return v
	case *inter.StringObject:
		return v
	default:
		// For other types, wrap in IdentifierObject for now
		// In a full implementation, we would need to handle all possible types
		return &inter.IdentifierObject{Value: ""}
	}
}

func lessThan(args []inter.Object, env *inter.Environment) inter.Object {
	if !checkRealNumber(args) {
		return &Error{Message: "all shoulbe be integer, rational, or real numbers"}
	}
	if len(args) < 2 {
		return &inter.BooleanObject{Value: true}
	}
	x := args[0].(*inter.NumberObject).Value
	for _, arg := range args[1:] {
		y := arg.(*inter.NumberObject).Value
		if num.Number_CMP(x, y) != -1 {
			return &inter.BooleanObject{Value: false}
		}
		x = y
	}
	return &inter.BooleanObject{Value: true}
}

func checkRealNumber(args []inter.Object) bool {
	for _, arg := range args {
		if arg.Type() == inter.NUMBER_OBJ {
			if _, ok := arg.(*inter.NumberObject).Value.(num.Complex); ok {
				return false
			}
		} else {
			return false
		}
	}
	return true
}

func lessThanOrEqual(args []inter.Object, env *inter.Environment) inter.Object {
	if !checkRealNumber(args) {
		return &Error{Message: "all shoulbe be integer, rational, or real numbers"}
	}
	if len(args) < 2 {
		return &inter.BooleanObject{Value: true}
	}
	x := args[0].(*inter.NumberObject).Value
	for _, arg := range args[1:] {
		y := arg.(*inter.NumberObject).Value
		if num.Number_CMP(x, y) == 1 {
			return &inter.BooleanObject{Value: false}
		}
		x = y
	}
	return &inter.BooleanObject{Value: true}
}

func greaterThan(args []inter.Object, env *inter.Environment) inter.Object {
	if !checkRealNumber(args) {
		return &Error{Message: "all shoulbe be integer, rational, or real numbers"}
	}
	if len(args) < 2 {
		return &inter.BooleanObject{Value: true}
	}
	x := args[0].(*inter.NumberObject).Value
	for _, arg := range args[1:] {
		y := arg.(*inter.NumberObject).Value
		if num.Number_CMP(x, y) != 1 {
			return &inter.BooleanObject{Value: false}
		}
		x = y
	}
	return &inter.BooleanObject{Value: true}
}

func greaterThanOrEqual(args []inter.Object, env *inter.Environment) inter.Object {
	if !checkRealNumber(args) {
		return &Error{Message: "all shoulbe be integer, rational, or real numbers"}
	}
	if len(args) < 2 {
		return &inter.BooleanObject{Value: true}
	}
	x := args[0].(*inter.NumberObject).Value
	for _, arg := range args[1:] {
		y := arg.(*inter.NumberObject).Value
		if num.Number_CMP(x, y) == -1 {
			return &inter.BooleanObject{Value: false}
		}
		x = y
	}
	return &inter.BooleanObject{Value: true}
}

func equal(args []inter.Object, env *inter.Environment) inter.Object {
	if checkRealNumber(args) {
		if len(args) < 2 {
			return &inter.BooleanObject{Value: true}
		}
		x := args[0].(*inter.NumberObject).Value
		for _, arg := range args[1:] {
			y := arg.(*inter.NumberObject).Value
			if num.Number_CMP(x, y) != 0 {
				return &inter.BooleanObject{Value: false}
			}
			x = y
		}
		return &inter.BooleanObject{Value: true}
	} else if checkPureComplexNumber(args) {
		if len(args) < 2 {
			return &inter.BooleanObject{Value: true}
		}
		x := args[0].(*inter.NumberObject).Value.(num.Complex)
		for _, arg := range args[1:] {
			y := arg.(*inter.NumberObject).Value.(num.Complex)
			if x != y {
				return &inter.BooleanObject{Value: false}
			}
		}
		return &inter.BooleanObject{Value: true}
	}
	return &Error{Message: "all shoulbe be integer, rational, or real numbers"}
}

func checkPureComplexNumber(args []inter.Object) bool {
	for _, arg := range args {
		if arg.Type() == inter.NUMBER_OBJ {
			if _, ok := arg.(*inter.NumberObject).Value.(num.Complex); !ok {
				return false
			}
		} else {
			return false
		}
	}
	return true
}
